﻿using BuildExplorer.Properties;
using Microsoft.TeamFoundation.Build.Client;
using Microsoft.TeamFoundation.Client;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace BuildExplorer
{
    public partial class OpenBuild : Form
    {
        public IBuildDetail SelectedBuild {get; private set; }
        public string RequestedBuild { get; set; }
        public Keys OkKeys { get; set; }

        private IBuildDetail[] buildDetails;

        public OpenBuild()
        {
            InitializeComponent();
        }

        private void OpenBuild_Load(object sender, EventArgs e)
        {
            DateFilterType d = Settings.Default.DateFilter;  // Save settings, since changing DataSource will trigger _dateFilter_SelectedIndexChanged, which will overwrite it
            _dateFilter.DataSource = Enum.GetValues(typeof(DateFilterType)).Cast<DateFilterType>();
            _dateFilter.SelectedItem = d;

            _txtCollection.Text = Settings.Default.CollectionUri;
            _txtProject.Text = Settings.Default.ProjectName;

            if (string.IsNullOrEmpty(_txtCollection.Text) || string.IsNullOrEmpty(_txtProject.Text))
            {
                PickProject();
            }

            if (!string.IsNullOrEmpty(RequestedBuild))
            {
                _txtSelectedBuild.Text = RequestedBuild;
                _btnOk_Click(sender, e);
                if (DialogResult != DialogResult.None)
                {
                    return;
                }
            }

            _txtSelectedBuild.Select();

            if (buildDetails != null)
            {
                PopulateDefinitions();  // Second dialog invocation - show what we already know
            }

            RefreshBuilds();
        }

        private void _btnOk_Click(object sender, EventArgs e)
        {
            OkKeys = Control.ModifierKeys;

            if (buildDetails != null)
            {
                SelectedBuild = buildDetails.FirstOrDefault(b => b.BuildNumber == _txtSelectedBuild.Text);
            }

            if (SelectedBuild == null)
            {
                // This happens if one types / pastes a build number to the edit box, possibly before RefreshBuilds is finished
                IBuildDetail[] builds;
                try
                {
                    builds = QueryBuilds(_txtSelectedBuild.Text);
                }
                catch (Exception ex)
                {
                    MessageBox.Show(this,
                        String.Format("Error getting builds:\n{0}", ex.Message),
                        "Build Explorer",
                        MessageBoxButtons.OK,
                        MessageBoxIcon.Error);

                    _btnConnect.Select();
                    return;
                }

                if (!builds.Any())
                {
                    MessageBox.Show(this,
                        String.Format("Build {0} not found", _txtSelectedBuild.Text),
                        "Build Explorer",
                        MessageBoxButtons.OK,
                        MessageBoxIcon.Error);

                    _txtSelectedBuild.Select();
                    return;
                }

                SelectedBuild = builds.First();
            }

            Settings.Default.Save();

            DialogResult = DialogResult.OK;
            Close();
        }

        private void _btnConnect_Click(object sender, EventArgs e)
        {
            if (PickProject() == DialogResult.OK)
            {
                RefreshBuilds();
            }
        }

        private DialogResult PickProject()
        {
            DialogResult res;

            using (var picker = new TeamProjectPicker(TeamProjectPickerMode.SingleProject, disableCollectionChange: false))
            {
                res = picker.ShowDialog(this);
                if (res == DialogResult.OK)
                {
                    _txtCollection.Text = picker.SelectedTeamProjectCollection.Uri.ToString();
                    _txtProject.Text = picker.SelectedProjects[0].Name;

                    Settings.Default.CollectionUri = _txtCollection.Text;
                    Settings.Default.ProjectName = _txtProject.Text;
                    Settings.Default.Save();

                    _listDefinitions.Items.Clear();
                    _listBuilds.Items.Clear();
                }
            }

            return res;
        }

        async void RefreshBuilds()
        {
            progressBar1.Show();

            try
            {
                buildDetails = await Task.Run(() => QueryBuilds());
            }
            catch (Exception ex)
            {
                MessageBox.Show(this,
                    String.Format("Error getting builds:\n{0}", ex.Message),
                    "Build Explorer",
                    MessageBoxButtons.OK,
                    MessageBoxIcon.Error);

                _btnConnect.Select();
                return;
            }
            finally
            {
                progressBar1.Hide();
            }

            PopulateDefinitions();
            Settings.Default.Save();
        }

        private IBuildDetail[] QueryBuilds(string buildNumber = null)
        {
            var collection = new TfsTeamProjectCollection(new Uri(_txtCollection.Text));
            var buildServer = collection.GetService<IBuildServer>();

            int buildNum;
            if (buildNumber != null && buildNumber.StartsWith("vstfs:///Build/Build/", StringComparison.OrdinalIgnoreCase))
            {
                return new IBuildDetail[] { buildServer.GetBuild(new Uri(buildNumber), null, QueryOptions.Definitions | QueryOptions.BatchedRequests) };
            }
            else if (buildNumber != null && int.TryParse(buildNumber, out buildNum))
            {
                return new IBuildDetail[] { buildServer.GetBuild(new Uri("vstfs:///Build/Build/" + buildNumber), null, QueryOptions.Definitions | QueryOptions.BatchedRequests) };
            }
            else 
            {
                var spec = buildServer.CreateBuildDetailSpec(Settings.Default.ProjectName);
                spec.QueryOptions = QueryOptions.Definitions
                                  | QueryOptions.BatchedRequests;  // Tell TFS2012 to populate RequestedFor / RequestedBy
                spec.QueryOrder = BuildQueryOrder.StartTimeDescending;
                spec.InformationTypes = null;

                if (buildNumber != null)
                {
                    // Specific build - simply get it
                    spec.BuildNumber = buildNumber.Trim();
                    return buildServer.QueryBuilds(spec).Builds;
                }

                spec.MinFinishTime = Settings.Default.DateFilter.GetMinFinishTime();
                spec.Status = BuildStatus.All;
                IBuildDetail[] builds = buildServer.QueryBuilds(spec).Builds;

                // If you provide MinFinishTime, you don't get InProgress builds - so query for them specifically
                if (spec.MinFinishTime != DateTime.MinValue)
                {
                    spec.MinFinishTime = DateTime.MinValue;
                    spec.Status = BuildStatus.InProgress | BuildStatus.NotStarted;
                    IBuildDetail[] inProgressBuilds = buildServer.QueryBuilds(spec).Builds;
                    builds = builds.Union(inProgressBuilds).ToArray();
                }

                return builds;
            }
        }

        private void PopulateDefinitions()
        {
            _listDefinitions.Items.Clear();

            if (buildDetails.Any(b => !b.BuildFinished))
            {
                _listDefinitions.Items.Add("(In progress)");
            }

            _listDefinitions.Items.AddRange(buildDetails
                .Select(b => Util.GetDefinitionName(b.BuildDefinition))
                .Distinct()
                .OrderBy(b => b)
                .ToArray());

            if (Settings.Default.SelectedDefinitions != null)
            {
                foreach (string def in Settings.Default.SelectedDefinitions)
                {
                    int index = _listDefinitions.Items.IndexOf(def);
                    if (index != -1)
                    {
                        _listDefinitions.SelectedIndices.Add(index);
                    }
                }
            }
        }

        private void _listDefinitions_SelectedIndexChanged(object sender, EventArgs e)
        {
            var selectedDefinitions = _listDefinitions.SelectedItems.Cast<string>();

            Settings.Default.SelectedDefinitions = new System.Collections.Specialized.StringCollection();
            foreach (string def in selectedDefinitions)
            {
                Settings.Default.SelectedDefinitions.Add(def);
            }

            bool inProgressSelected = selectedDefinitions.Contains("(In progress)");

            IEnumerable<IBuildDetail> builds = buildDetails
                .Where(b => (inProgressSelected && !b.BuildFinished)
                          || selectedDefinitions.Contains(Util.GetDefinitionName(b.BuildDefinition)))
                .OrderByDescending(b => b.StartTime);

            _listBuilds.BeginUpdate();

            _listBuilds.Items.Clear();
            foreach (var build in builds)
            {
                int imageIndex;
                switch (build.Status)
                {
                    case BuildStatus.Succeeded: imageIndex = 0; break;
                    case BuildStatus.Failed: imageIndex = 1; break;
                    case BuildStatus.InProgress: imageIndex = 2; break;
                    case BuildStatus.Stopped: imageIndex = 3; break;
                    case BuildStatus.PartiallySucceeded: imageIndex = 4; break;
                    default: imageIndex = -1; break;
                }

                var item = new ListViewItem(string.Empty, imageIndex);
                item.SubItems.Add(build.Uri.ToString().Split('/').Last());
                item.SubItems.Add(build.BuildNumber);
                item.SubItems.Add(build.RequestedFor);
                item.SubItems.Add(Util.GetDefinitionName(build.BuildDefinition));
                item.SubItems.Add(Util.FormatDateTime(build.StartTime.ToUniversalTime()));
                item.SubItems.Add(Util.FormatDateTime(build.FinishTime.ToUniversalTime(), DateTime.Now - build.StartTime));
                item.SubItems.Add(build.Quality);
                item.SubItems.Add(build.SourceGetVersion);
                item.SubItems.Add(build.ShelvesetName);

                _listBuilds.Items.Add(item);
            }

            foreach (ColumnHeader column in _listBuilds.Columns)
            {
                column.AutoResize(ColumnHeaderAutoResizeStyle.ColumnContent);
            }

            _listBuilds.EndUpdate();
        }

        private void _btnRefresh_Click(object sender, EventArgs e)
        {
            RefreshBuilds();
        }

        private void listView1_SelectedIndexChanged(object sender, EventArgs e)
        {
            if (_listBuilds.SelectedItems.Count > 0)
            {
                _txtSelectedBuild.Text = _listBuilds.SelectedItems[0].SubItems[1].Text;
            }
        }

        private void listView1_DoubleClick(object sender, EventArgs e)
        {
            _btnOk_Click(sender, e);
        }

        private void _dateFilter_Format(object sender, ListControlConvertEventArgs e)
        {
            var x = (DateFilterType)e.ListItem;
            e.Value = Util.GetEnumDescription(x);
        }

        private void _dateFilter_SelectedIndexChanged(object sender, EventArgs e)
        {
            Settings.Default.DateFilter = (DateFilterType)_dateFilter.SelectedItem;
        }

        private void _txtSelectedBuild_KeyDown(object sender, KeyEventArgs e)
        {
            if (e.KeyData == (Keys.Enter | Keys.Control))
            {
                _btnOk_Click(sender, e);
            }
        }
    }
}
